﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Scryber.Data
{
    /// <summary>
    /// Base class for all XPath data providers. 
    /// </summary>
    /// <remarks>The PDFXMLDataSource instance uses types inheriting from this base class to load the required data</remarks>
    public abstract class PDFXPathDataProviderBase : Scryber.Components.PDFComponent
    {
        /// <summary>
        /// Gets the flag that identifies if the this provider 
        /// </summary>
        public bool SupportsDataSchema {get; private set;}

        /// <summary>
        /// Gets last XPath data that was loaded from this provider.
        /// Use the Reset method to clear the value.
        /// </summary>
        protected System.Xml.XPath.XPathNavigator LastResult { get; private set; }

        /// <summary>
        /// Gets the last schema data result from this provider.
        /// Use the Reset method to clear the value.
        /// </summary>
        protected Scryber.PDFDataSchema LastSchema { get; private set; }

        public PDFXPathDataProviderBase(PDFObjectType type, bool supportsSchema) : base(type)
        {
            this.SupportsDataSchema = supportsSchema;
        }

        protected string LogCategory()
        {
            string category = "XPath Data Provider";
            if (null != this.Name)
                category += " " + this.Name;
            return category;
        }

        public System.Xml.XPath.XPathNavigator LoadData(PDFDataContext context)
        {
            string category = this.LogCategory();

            System.Xml.XPath.XPathNavigator result = this.LastResult;
            
            if (null == result)
            {
                try
                {
                    if (context.ShouldLogVerbose)
                        context.TraceLog.Begin(TraceLevel.Message, category, "Loading the source data from the provider");

                    bool retain;

                    using (IDisposable monitor = context.PerformanceMonitor.Record(PerformanceMonitorType.Data_Load, category))
                    {
                        result = DoLoadData(context, out retain);
                    }

                    if (context.ShouldLogVerbose)
                        context.TraceLog.End(TraceLevel.Message, category, "XPath Data Provider " + this.Name + "Finished loading the source data from the provider");
                    else if (context.ShouldLogMessage)
                        context.TraceLog.Add(TraceLevel.Message, "Data Provider " + this.Name, "Loading the source data from the provider");

                    if (retain)
                        this.LastResult = result;
                }
                catch (PDFDataException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new PDFDataException(String.Format(Errors.CouldNotLoadTheDataFromProvider, this.ID), ex);
                }
            }

            return result;
        }

        /// <summary>
        /// Clears any previous retained result from this provider. Any request to LoadData must then load the 
        /// </summary>
        protected virtual void Reset()
        {
            this.LastResult = null;
        }

        /// <summary>
        /// Abstract method that inheritors must implement to actually load the 
        /// data from their respective providers.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="retain"></param>
        /// <returns></returns>
        protected abstract System.Xml.XPath.XPathNavigator DoLoadData(PDFDataContext context, out bool retain);


        /// <summary>
        /// Returns any data schema for this provider based on it's settings. If this provider does not 
        /// support data schemas, then null (nothing) will be returned.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public Scryber.PDFDataSchema LoadSchema(PDFDataContext context)
        {
            string category = this.LogCategory();

            if (!this.SupportsDataSchema)
                return null;

            Scryber.PDFDataSchema result = this.LastSchema;

            if (null == result)
            {
                try
                {
                    if (context.ShouldLogVerbose)
                        context.TraceLog.Begin(TraceLevel.Message, category, "Loading the schema data from the provider");

                    bool retain;

                    using (IDisposable monitor = context.PerformanceMonitor.Record(PerformanceMonitorType.Data_Load, category))
                    {
                        result = DoLoadSchema(context, out retain);
                    }

                    if (context.ShouldLogVerbose)
                        context.TraceLog.End(TraceLevel.Message, category, "XPath Data Provider finished loading the schema from the provider");
                    else if (context.ShouldLogMessage)
                        context.TraceLog.Add(TraceLevel.Message, "Data Provider " + this.Name, "Loaded the schema data from the provider");

                    if (retain)
                        this.LastSchema = result;
                }
                catch (PDFDataException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new PDFDataException(String.Format(Errors.CouldNotLoadTheDataFromProvider, this.ID), ex);
                }

            }

            return result;
        }

        /// <summary>
        /// Virtual method that all data providers must implement if they are to support data schemas.
        /// Do not call the base method if overriding.
        /// </summary>
        /// <param name="context">The current data context</param>
        /// <param name="retain">Set this to true is the returned schema value should be retained for any future calls</param>
        /// <returns></returns>
        protected virtual Scryber.PDFDataSchema DoLoadSchema(PDFDataContext context, out bool retain)
        {
            if (this.SupportsDataSchema)
                throw new InvalidOperationException("This provider is decalred as supporting schema extraction, but implementing provider has not provided impliementation");

            retain = false;
            return null;
        }

        
    }

    /// <summary>
    /// A collection of providers
    /// </summary>
    public class PDFXPathDataProviderCollection : List<PDFXPathDataProviderBase>
    {

        public PDFXPathDataProviderCollection(): base()
        { }

        #region public bool TryGetDefaultProvider(out PDFXPathDataProviderBase provider)

        /// <summary>
        /// Returns true with the provider set to first item in the collection, if there is one. Otherwise returns false and provider is null.
        /// </summary>
        /// <param name="provider">Set to the first provider in this collection if any</param>
        /// <returns>True if this collection has at least one item (and that item is not null</returns>
        public bool TryGetDefaultProvider(out PDFXPathDataProviderBase provider)
        {
            provider = null;

            if (this.Count > 0)
            {
                provider = this[0];
            }
            return null != provider;
        }

        #endregion

        #region public bool TryGetProviderByName(string name, out PDFXPathDataProviderBase provider)

        /// <summary>
        /// Looks for a provider in this collection that has a matching name. Returns true if one was found.
        /// </summary>
        /// <param name="name">The name of the provider to look for - if null or empty this method will always return false</param>
        /// <param name="provider">Set to any found matching provider (or null if not found)</param>
        /// <returns>True if a provider was found, otherwise false</returns>
        public bool TryGetProviderByName(string name, out PDFXPathDataProviderBase provider)
        {

            provider = null;

            if (string.IsNullOrEmpty(name))
                return false;

            if(this.Count > 0)
            {
                foreach (PDFXPathDataProviderBase item in this)
                {
                    if (!string.IsNullOrEmpty(item.Name) && item.Name.Equals(name))
                    {
                        provider = item;
                        break;
                    }
                }
            }

            return null != provider;
        }

        #endregion
    }

    /// <summary>
    /// Loads SQL data from a database and builds a dataset which is then converted to XPathNavigator.
    /// </summary>
    [PDFParsableComponent("SqlDataProvider")]
    public class PDFXPathSQLDataProvider : PDFXPathDataProviderBase
    {

        public PDFXPathSQLDataProvider()
            : base((PDFObjectType)"xpsq", true)
        {
        }

        public override System.Xml.XPath.XPathNavigator DoLoadData(PDFDataContext context)
        {
            throw new NotImplementedException();
        }
    }
}
